<?php

	namespace org\tekuna\framework\request;

	use org\tekuna\base\Tekuna;
	use org\tekuna\framework\filter\Filter;
	use org\tekuna\framework\filter\FileFilter;
	use org\tekuna\framework\filter\file\EmptyFileFilter;
	use org\tekuna\framework\filter\data\FrameworkActionFilter;

	/**
	 * Request data abstraction object. Abstracts
	 *
	 *   - GET data
	 *   - POST data
	 *   - uploaded files
	 *
	 * For each single field within the different scopes can be
	 * defined some filters that are evaluated implicitly while
	 * delivering the requested values.
	 */
	class HttpRequest extends Request {

		const ACTION_REQUEST_PARAMETER = 'action';

		protected
			$objLogger = NULL,
			$arrRawGet = array(),
			$arrRawPost = array(),
			$arrFiles = array(),
			$sRequestProtocol = 'HTTP/1.1',
			$sRequestMethod = '',
			$sAuthenticatedUser = NULL,
			$sAuthenticatedPassword = NULL,
			$bIsHttps = FALSE,

			$arrGetFilters = array(),
			$arrPostFilters = array(),
			$arrFileFilters = array(),

			$objDefaultFileFilter = NULL,
			$sRequestAction = '';


		/**
		 * Request's constructor. The values of the superglobal arrays
		 * for GET, POST, REQUEST and FILES are imported and resetted
		 * afterwards. The internal encoding is set to UTF-8. The effects
		 * of magic quotes (runtime and gpc) are removed to privide
		 * configuration-independent "clean" values.
		 *
		 * For setting the internal encoding, the mbstring extension is
		 * needed. If the extension is not loaded, a RequestException
		 * is thrown.
		 */
		protected function init() {

			global $HTTP_GET_VARS;
			global $HTTP_POST_VARS;
			global $HTTP_REQUEST_VARS;
			global $HTTP_FILES_VARS;

			// initialize the logger
			$this -> objLogger = Tekuna :: getLogger(__CLASS__);

			// import the raw values
			$this -> arrRawGet = $_GET;
			$this -> arrRawPost = $_POST;

			// store request protocol if given
			if (isset($_SERVER['SERVER_PROTOCOL'])) {

				$this -> sRequestProtocol = $_SERVER['SERVER_PROTOCOL'];
			}

			// set request method
			$this -> sRequestMethod = strtoupper($_SERVER['REQUEST_METHOD']);

			// set authenticated user and password if given
			if (isset($_SERVER['PHP_AUTH_USER'])) {

				$this -> sAuthenticatedUser = $_SERVER['PHP_AUTH_USER'];
			}

			if (isset($_SERVER['PHP_AUTH_PW'])) {

				$this -> sAuthenticatedPassword = $_SERVER['PHP_AUTH_PW'];
			}

			if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {

				$this -> bIsHttps = TRUE;
			}

			// set default filters
			$this -> objDefaultFileFilter = new EmptyFileFilter();
			$this -> setGetFilter(self :: ACTION_REQUEST_PARAMETER, new FrameworkActionFilter());

			// store requested action
			$this -> sRequestAction = $this -> getGetValue(self :: ACTION_REQUEST_PARAMETER);
			$this -> objLogger -> info("Starting Request for action '$this->sRequestAction'.");

			// import file data
			$this -> importUploadedFiles();

			// clear raw values
			$_GET = array();
			$_POST = array();
			$_REQUEST = array();
			$_FILES = array();

			$HTTP_GET_VARS = array();
			$HTTP_POST_VARS = array();
			$HTTP_REQUEST_VARS = array();
			$HTTP_FILES_VARS = array();

			// clear raw POST array if available
			if (isset($GLOBALS['HTTP_RAW_POST_DATA'])) {

				$GLOBALS['HTTP_RAW_POST_DATA'] = array();
			}
		}


		protected function importUploadedFiles() {

			if (isset($_FILES) && is_array($_FILES)) {

				// import files
				foreach ($_FILES as $sID => $arrData) {

					// correct \' to ' on Linux with magic_quotes_gpc enabled
					if (get_magic_quotes_gpc()) {

						$arrData['name'] = stripslashes($arrData['name']);
					}

					$this -> arrFiles[$sID] = new PostFile($sID, $arrData);
				}
			}
		}


		/**
		 * @return boolean returns TRUE if the request was performed by HTTPS.
		 */
		public function isHttpsRequest() {

			return $this -> bIsHttps;
		}


		/**
		 * @return string returns the request protocol and revision (e.g. HTTP/1.1).
		 */
		public function getRequestProtocol() {

			return $this -> sRequestProtocol;
		}


		/**
		 * @return string returns the request method of the current request; value is GET or POST
		 */
		public function getRequestMethod() {

			return $this -> sRequestMethod;
		}


		/**
		 * @return returns the current request action
		 */
		public function getRequestAction() {

			return $this -> sRequestAction;
		}


		/**
		 * @return boolean returns true if the current request is performed by GET.
		 */
		public function isGetRequest() {

			return ($this -> sRequestMethod === 'GET');
		}


		/**
		 * @return boolean returns true if the current request is performed by POST.
		 */
		public function isPostRequest() {

			return ($this -> sRequestMethod === 'POST');
		}


		/**
		 * @return boolean returns true if there is basic user authentication data available.
		 */
		public function hasAuthenticatedUser() {

			return ($this -> sAuthenticatedUser !== NULL);
		}


		/**
		 * @return string returns the user name of the authenticated user
		 */
		public function getAuthenticatedUser() {

			return $this -> sAuthenticatedUser;
		}


		/**
		 * @return string returns the password of the authenticated user
		 */
		public function getAuthenticatedPassword() {

			return $this -> sAuthenticatedPassword;
		}


		/**
		 * Set a filter object for a certain field in the GET scope.
		 *
		 * @param string $sField the field in the GET scope
		 * @param Filter a filter object
		 */
		public function setGetFilter($sField, Filter $objFilter) {

			$this -> arrGetFilters[$sField] = $objFilter;
		}


		/**
		 * Returns the filter for a specific field in the GET scope. If
		 * no special filter is set (and implicitly the default filter
		 * would be applied) this method returns NULL.
		 *
		 * @param string $sField the field in the GET scope
		 * @return Filter object or NULL
		 */
		public function getGetFilter($sField) {

			if (isset($this -> arrGetFilters[$sField])) {

				return $this -> arrGetFilters[$sField];
			}
			else {

				return NULL;
			}
		}


		/**
		 * Set a filter object for a certain field in the POST scope.
		 *
		 * @param string $sField the field in the POST scope
		 * @param Filter a filter object
		 */
		public function setPostFilter($sField, Filter $objFilter) {

			$this -> arrPostFilters[$sField] = $objFilter;
		}


		/**
		 * Returns the filter for a specific field in the POST scope. If
		 * no special filter is set (and implicitly the default filter
		 * would be applied) this method returns NULL.
		 *
		 * @param string $sField the field in the POST scope
		 * @return Filter object or NULL
		 */
		public function getPostFilter($sField) {

			if (isset($this -> arrPostFilters[$sField])) {

				return $this -> arrPostFilters[$sField];
			}
			else {

				return NULL;
			}
		}


		/**
		 * Set a filter object for an uploaded file
		 *
		 * @param string $sFile the identifier of the file
		 * @param Filter a filter object for this file
		 */
		public function setFileFilter($sFile, FileFilter $objFilter) {

			$this -> arrFileFilters[$sFile] = $objFilter;
		}


		/**
		 * Returns the filter for a specific uploaded file. If no
		 * special filter is set (and implicitly the default file
		 * filter would be applied) NULL is returned.
		 *
		 * @param string $sFile the identifier of the file
		 * @return TekunaFileFilter object or NULL
		 */

		public function getFileFilter($sFile) {

			if (isset($this -> arrFileFilters[$sFile])) {

				return $this -> arrFileFilters[$sFile];
			}
			else {

				return NULL;
			}
		}


		/**
		 * Set the default filter that is applied, if no special filter
		 * for a specific uploaded file is set
		 *
		 * @param TekunaFileFilter $objFilter the new filter object
		 */
		public function setDefaultFileFilter(FileFilter $objFilter) {

			$this -> objDefaultFileFilter = $objFilter;
		}


		/**
		 * @return TekunaFileFilter returns the current active default file filter for uploaded files.
		 */
		public function getDefaultFileFilter() {

			return $this -> objDefaultFileFilter;
		}


		/**
		 * Checks if a specific value in the GET scope is available.
		 *
		 * @param string $sField the field in the GET scope
		 * @return boolean returns true if the value is available
		 */
		public function isGetValueAvailable($sField) {

			return isset($this -> arrRawGet[$sField]);
		}


		public function hasGetValue($sField) {

			return isset($this -> arrRawGet[$sField]);
		}
		
		
		/**
		 * Returns a value from the GET scope. If the value is not available
		 * a RequestException is thrown. The defined filter (or the default filter)
		 * is applied to the value and (if it is an array) all values in any depth.
		 *
		 * @param string $sField the field in the GET scope
		 * @return string the value of this field
		 */
		public function getGetValue($sField) {

			if (! isset($this -> arrRawGet[$sField])) {

				throw new RequestException("Key '$sField' is not available in GET scope.");
			}

			$objFilter = NULL;
			if (isset($this -> arrGetFilters[$sField])) {

				$objFilter = $this -> arrGetFilters[$sField];
			}
			else {

				$objFilter = $this -> objDefaultFilter;
			}

			return $this -> applyFilterRecursive($this -> arrRawGet[$sField], $objFilter);
		}


		/**
		 * @return array returns an associative array of all available values in the GET scope.
		 */
		public function getAllGetValues() {

			$arrReturn = array();

			foreach (array_keys($this -> arrRawGet) as $sField) {

				$arrReturn[$sField] = $this -> getGetValue($sField);
			}

			return $arrReturn;
		}


		/**
		 * Checks if a specific value in the POST scope is available.
		 *
		 * @param string $sField the field in the POST scope
		 * @return boolean returns true if the value is available
		 */
		public function isPostValueAvailable($sField) {

			return isset($this -> arrRawPost[$sField]);
		}


		public function hasPostValue($sField) {

			return isset($this -> arrRawPost[$sField]);
		}
		
		
		/**
		 * Returns a value from the POST scope. If the value is not available
		 * a RequestException is thrown. The defined filter (or the default filter)
		 * is applied to the value and (if it is an array) all values in any depth.
		 *
		 * @param string $sField the field in the POST scope
		 * @return string the value of this field
		 */
		public function getPostValue($sField) {

			if (! isset($this -> arrRawPost[$sField])) {

				throw new RequestException("Key '$sField' not available in POST scope.");
			}

			$objFilter = NULL;
			if (isset($this -> arrPostFilters[$sField])) {

				$objFilter = $this -> arrPostFilters[$sField];
			}
			else {

				$objFilter = $this -> objDefaultFilter;
			}

			return $this -> applyFilterRecursive($this -> arrRawPost[$sField], $objFilter);
		}


		/**
		 * @return array returns an associative array of all available values in the POST scope.
		 */
		public function getAllPostValues() {

			$arrReturn = array();

			foreach (array_keys($this -> arrRawPost) as $sField) {

				$arrReturn[$sField] = $this -> getPostValue($sField);
			}

			return $arrReturn;
		}


		/**
		 * Checks if a specific uploaded file is available.
		 *
		 * @param string $sFileID the file identifier
		 * @return boolean returns true if the value is available
		 */
		public function hasFile($sFileID) {

			return isset($this -> arrFiles[$sFileID]);
		}


		/**
		 * Returns PostFile object of an uploaded file. If the file is not available
		 * a RequestException is thrown. The defined filter (or the default file filter)
		 * is applied to the value.
		 *
		 * @param string $sFileID the identifier of the file
		 * @return PostFile the file object
		 */
		public function getFile($sFileID) {

			if (! isset($this -> arrFiles[$sFileID])) {

				throw new RequestException("File '$sFileID' does not exist.");
			}

			$objFilter = NULL;
			if (isset($this -> arrFileFilters[$sFileID])) {

				$objFilter = $this -> arrFileFilters[$sFileID];
			}
			else {

				$objFilter = $this -> objDefaultFileFilter;
			}

			return $objFilter -> filter($this -> arrFiles[$sFileID]);
		}


		/**
		 * @return array returns an associative array of all available uploaded files.
		 */
		public function getAllFiles() {

			$arrReturn = array();

			foreach (array_keys($this -> arrFiles) as $sFileID) {

				$arrReturn[$sFileID] = $this -> getFile($sFileID);
			}

			return $arrReturn;
		}
		
		
		public function buildCurrentRequestUrl() {
		
			$objUrl = new Url();
			
			if ($this -> isHttpsRequest()) {
				
				$objUrl -> setProtocol('https');
			}
			else {
				
				$objUrl -> setProtocol('http');
			}
			
			$objUrl -> setUser($this -> sAuthenticatedUser);
			$objUrl -> setPassword($this -> sAuthenticatedPassword);
			$objUrl -> setHost($_SERVER['SERVER_NAME']);
			$objUrl -> setPort($_SERVER['SERVER_PORT']);
			
			// truncate parameters from the path
			$sPath = $_SERVER['REQUEST_URI'];
			if (strpos($sPath, '?') !== false) {
				
				$sPath = substr($sPath, 0, strpos($sPath, '?'));
			}
			$objUrl -> setPath($sPath);
			
			// omit the action parameter
			$arrParameters = $this -> getAllGetValues();
			unset($arrParameters[self :: ACTION_REQUEST_PARAMETER]);
			$objUrl -> setParameters($arrParameters);
			
			return $objUrl;
		}
	}


